﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Data;
using System.Xml;
using System.IO;

namespace fes
{
  public class ExpertSystem
  {
    private StockData sd = new StockData();
    private List<String> syms = new List<string>();  // list of symbol names
    private Dictionary<String, List<double[]>> PastIndicatorValues = new Dictionary<string,List<double[]>>();
    private Dictionary<String, Best> Bests = new Dictionary<String, Best>();
    private Dictionary<String, double[]> BuyNHoldValues = new Dictionary<String, double[]>();
    private Dictionary<String, double> lastPrices = new Dictionary<string, double>();
    private Dictionary<String, int> position = new Dictionary<string,int>(); // number of hold stock shares
    private Dictionary<String, List<Indicator>> indicators = new Dictionary<string,List<Indicator>>();
    private List<Indicator> teachIndicators = new List<Indicator>();
    private List<FuzzyAssociativeMemory> fams = new List<FuzzyAssociativeMemory>();
    private NNStockMarket nn;
    private int _TeachNetAfter;
    private double tradingCost = 0.003; // 0.3% - default value
    private double _initialCash;
    private double _currentCash;
    private double _BuyNHoldBegin=-1;
    private double _BuyNHoldEnd=-1;

    private bool _isTeached;
    private bool _IsSet = false;



    public bool IsTeached
    {
      get { return _isTeached; }
      set { _isTeached = value; }
    }

    public double CurrentCash
    {
      get { return _currentCash; }
      set { _currentCash = value; }
    }

    public List<FuzzyAssociativeMemory> FAMs
    {
      get { return fams; }
      set { fams = value; }
    }

    public int TeachNetAfter
    {
      get { return _TeachNetAfter; }
      set { _TeachNetAfter = value; }
    }


    public double TradingCost
    {
      get { return tradingCost; }
      set { tradingCost = value; }
    }

    public double InitialCash
    {
      get { return _initialCash; }
      set { _initialCash = value; _currentCash = value; }
    }

    //-------------------------------------------
      //dla roznych strategii:

    private double _NeuralCash;
    public double NeuralCash { get { return _NeuralCash; } }
    private int _NeuralSharesNumber;
    public int NeuralSharesNumber { get { return _NeuralSharesNumber; } }
    private double _NeuralDecision;
    public double NeuralDecision { get { return _NeuralDecision; } }
    private double _BestCash;
    public double BestCash { get { return _BestCash; } }
    private int _BestSharesNumber;
    public int BestSharesNumber { get { return _BestSharesNumber; } }
    private double _HossaBessaCash;
    public double HossaBessaCash { get { return _HossaBessaCash; } }
    private int _HossaBessaSharesNumber;
    public int HossaBessaSharesNumber { get { return _HossaBessaSharesNumber; } }
    private double _BuyNHoldCash;
    public double BuyNHoldCash { get { return _BuyNHoldCash; } }
    private int _BuyNHoldSharesNumber;
    public int BuyNHoldSharesNumber { get { return _BuyNHoldSharesNumber; } }


    //-------------------------------------------


    public bool IsSet
    {
      get { return _IsSet; }
      set { _IsSet = value; }
    }

      //publicznie bo musimy to umieć poustawiać...
      //nie wiem jednak jak zrobić, aby nie można było dodawać do tej listy tak bez niczego
    public NNStockMarket NeuralNetwork { get { return nn; } }



    public int IndicatorsCount { get {return indicators.Count;} }
    public int IndicatorPairs { get {return indicators.Count/2;}}
    public int FamsCount {get{return fams.Count;}}
    //public int StockDataCount {get{return sd.getSize();}}

    private String SystemName;
    public String Name { get { return SystemName; } set { SystemName = value; } }

    public ExpertSystem(String name)
    {
      SystemName = name;
    }
    
    public ExpertSystem(String name, List<String> s)
    {
      SystemName = name;
      syms = s;
      foreach (String sym in syms)
      {
        indicators[sym] = new List<Indicator>();
        Bests[sym] = new Best();
        BuyNHoldValues[sym] = new double[2];
        BuyNHoldValues[sym][0] = -1;
      }
    }

    public Indicator getIndicator(int index)
    {
      if (index >= 0)
        return indicators[syms[0]][index];
      return null;
    }

    public void setIndicator(int index, Indicator ind)
    {
      foreach (String sym in syms)
      {
        indicators[sym].Add(ind);
      }
    }

    public void removeIndicatorRange(int s, int n)
    {
      foreach (String sym in syms)
      {
        indicators[sym].RemoveRange(s, n);
      }
    }


    public bool loadConfiguration(String ConfigurationFile) 
    {
        /*if (!File.Exists(ConfigurationFile))
        {
            this.IsSet = false;
            MessageBox.Show("Wybrany plik nie istnieje");
            return false;
        }

        try
        {
            XmlReaderSettings settings = new XmlReaderSettings();
            settings.IgnoreComments = true;
            settings.IgnoreWhitespace = true;
            settings.IgnoreProcessingInstructions = true;

            XmlReader reader = XmlReader.Create(ConfigurationFile, settings);


            reader.ReadToFollowing("expertsystem");
            if ((!reader.HasAttributes) || (reader.AttributeCount != 2)) return false;
            _numOfInputs = Convert.ToInt32(reader.GetAttribute("inputs"));
            _numOfLayers = Convert.ToInt32(reader.GetAttribute("layers"));

            Net = new List<Neuron>[_numOfLayers];
            Inputs = new double[_numOfInputs];
            for (int i = 0; i < _numOfLayers; i++)
            {
                Net[i] = new List<Neuron>();
            }
            this.actNumOfLayers = 0;

            int neuronsN;
            int inputsN;
            int all = 0;
            reader.Read();
            for (int j = 0; j < _numOfLayers; j++)
            {
                //reader.ReadStartElement("layer");

                if (!reader.HasAttributes) return false;

                if (j == 0) inputsN = _numOfInputs;
                else inputsN = Net[j - 1].Count;
                neuronsN = Convert.ToInt32(reader.GetAttribute("neurons"));
                double[] w = new double[neuronsN * inputsN];
                all += neuronsN * inputsN;
                reader.Read();
                for (int k = 0; k < neuronsN; k++)
                {
                    reader.ReadStartElement("neuron");
                    //reader.Read();
                    for (int i = 0; i < inputsN; i++)
                    {
                        reader.ReadStartElement("weight");
                        w[inputsN * k + i] = Convert.ToDouble(reader.ReadString());
                        reader.ReadEndElement();
                    }

                    reader.ReadEndElement(); //neuron
                }
                InitLayer(neuronsN, w);
                reader.ReadEndElement(); //layer
            }
            reader.ReadEndElement();
            _numOfInputsGlobal = all;
            reader.Close();
        }
        catch (Exception e)
        {
            Console.WriteLine("Parsing problem: " + e.Message);
            return false;
        }
        ready = true;
        _numOfOutputs = Net[_numOfLayers - 1].Count;
        Outputs = new double[_numOfOutputs];
        */
        return true;

    }

    public bool LoadData(String DataFile)
    {
        
        return true;
    }

    public bool SaveExpertSystem(String ConfigurationFile) 
    {
    // todo: zapis konfiguracji
        return true;
    }
    

    public int AddIndicatorPair(Indicator indicator1, Indicator indicator2)
    {
      teachIndicators.Add(indicator1);
      teachIndicators.Add(indicator2);
      fams.Add(new FuzzyAssociativeMemory(indicator1, indicator2));
      foreach (String sym in syms)
      {
        indicators[sym].Add(indicator1);
        indicators[sym].Add(indicator2);

      }
      return fams.Count - 1;
    }

    public bool SetNeuralNetwork(int layers, int teachNetAfter) 
    {
        if (teachIndicators.Count % 2 != 0) return false;
        if (fams.Count != teachIndicators.Count / 2) return false;

        nn = new NNStockMarket(fams.Count, layers);
        _TeachNetAfter = teachNetAfter;
        _IsSet = true;

        return true;
    }

    public bool SetNeuralNetwork(NNStockMarket TheNetwork, int teachNetAfter) 
    {
        if (TheNetwork.numOfInputs != FamsCount) return false;

        _TeachNetAfter = teachNetAfter;
        nn = TheNetwork;
        _IsSet = true;
        return true;
    }

    public double PushNextDataForSym(String sym, DateTime date, double open, double high, double low, double close, int vol)
    {

        Bests[sym].PushNext(close, date);
        if (BuyNHoldValues[sym][0] < 0) BuyNHoldValues[sym][0] = close;
        BuyNHoldValues[sym][1] = close;


      double r = 0;
      try

      {
        if (Double.IsNaN(close))
          return 0.0;
        if (!position.Keys.Contains(sym))
          position[sym] = 0;
        lastPrices[sym] = close;
        List<double> inputs = new List<double>();
        foreach (Indicator ind in indicators[sym])
        {
          ind.pushNextDayData(open, high, low, close, vol);
          ind.fuzzify(ind.getValue());
        }

        for (int i = 0; i < fams.Count; i++)
        {
          //fams[i].FuzzyAssociativeMemoryInputs(indicators[sym][i * 2], indicators[sym][i * 2 + 1]);
          //fams[i].reason();
          //inputs.Add(fams[i].defuzzify());
          FuzzyAssociativeMemory fam = new FuzzyAssociativeMemory(indicators[sym][i * 2], indicators[sym][i * 2 + 1]);
          fam.reason();
          inputs.Add(fam.defuzzify());
        }

        r = nn.MakeDecision(inputs.ToArray());
        if (r > 0)
        {
          int sn = Math.Abs((int)((0.20 * r * _currentCash) / close));
          position[sym] += sn;

          _currentCash -= sn * close;
          _currentCash -= tradingCost * sn * close;

          if (sn > 0)
            Console.WriteLine(date.ToLongDateString() + ": Kupno: " + sn + " akcji " + sym + " za " + close);
        }
        else if (r < 0)
        {
          int sn = (int)(-0.2 * r * position[sym]);
          if (sn > position[sym])
            sn = position[sym];

          position[sym] -= sn;
          _currentCash += sn * close;
          _currentCash -= 0.003 * sn * close;
          if (sn > 0)
            Console.WriteLine(date.ToLongDateString() + ": Sprzedaż: " + sn + " akcji " + sym + " za " + close);
        }
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message);
      }

      return r;
    }

    public bool PushNextData(String sym, DateTime date, double open, double high, double low, double close, int vol) 
    { 
      if (!_IsSet) return false;

      for (int i = 0; i < teachIndicators.Count; i++)
      {
        teachIndicators[i].pushNextDayData(open, high, low, close, vol);
        teachIndicators[i].fuzzify(teachIndicators[i].getValue());
      }

      List<double> inputs = new List<double>();
      for (int i = 0; i < fams.Count; i++) 
      {
        fams[i].FuzzyAssociativeMemoryInputs(teachIndicators[i * 2], teachIndicators[i * 2 + 1]);
        fams[i].reason();
        inputs.Add(fams[i].defuzzify());
      }

      // ---------neural net
      sd.AddData(date,open,high,low,close,vol);

      if (!PastIndicatorValues.ContainsKey(sym))
        PastIndicatorValues[sym] = new List<double[]>();
      PastIndicatorValues[sym].Add(inputs.ToArray());
      if ((sd.getSize() >= _TeachNetAfter) && (PastIndicatorValues[sym].Count >= _TeachNetAfter))
      {
        nn.Teach(sd.getCloseForDay(0), close, PastIndicatorValues[sym][0]);
        sd.CutDataHead(sd.getSize()-_TeachNetAfter+1); //na wszelki wypadek, gdyby list z jakis powodow sie rozrosla
        PastIndicatorValues[sym].RemoveAt(0); 
      }

      //TODO: to tutaj nie jest chyba potrzebne
      //nn.MakeDecision(PastIndicatorValues[sym][PastIndicatorValues[sym].Count - 1]);
 
      return true;
    }

    public void updatePortfolio(DataGridView dgdPortfolio)
    {
      int i = 0;
      foreach (KeyValuePair<String, int> kvp in position)
      {
        if (i == dgdPortfolio.Rows.Count)
          dgdPortfolio.Rows.Add();
        dgdPortfolio.Rows[i].Cells[0].Value = kvp.Key;
        dgdPortfolio.Rows[i].Cells[1].Value = kvp.Value;
        dgdPortfolio.Rows[i].Cells[2].Value = lastPrices[kvp.Key];
        dgdPortfolio.Rows[i].Cells[3].Value = Math.Round(kvp.Value * lastPrices[kvp.Key], 2);
        i++;
      }
      if (i == dgdPortfolio.Rows.Count)
        dgdPortfolio.Rows.Add();
      dgdPortfolio.Rows[i].Cells[0].Value = "Gotówka";
      dgdPortfolio.Rows[i].Cells[1].Value = "";
      dgdPortfolio.Rows[i].Cells[2].Value = "";
      dgdPortfolio.Rows[i].Cells[3].Value = Math.Round(_currentCash, 2);      
    }


    public double getPortfolioValue()
    {
      double val = _currentCash;
      foreach (KeyValuePair<String, int> kvp in position)
      {
        val += kvp.Value * lastPrices[kvp.Key];
      }
      return Math.Round(val, 2);
    }

    public void newSimulation()
    {
      _currentCash = _initialCash;
      position.Clear();
      lastPrices.Clear();
    }

    public double getProfit()
    {
      return Math.Round((((getPortfolioValue()) - _initialCash) / _initialCash * 100.0), 2);
    }

    private void NeuralStrategy(){}
    public Best BestStrategy(String sym)
    {
        Bests[sym].CalculateBest();
        return Bests[sym];

    }
    public double BuyNHoldStrategy(String sym) { return ((BuyNHoldValues[sym][1] - BuyNHoldValues[sym][0]) / BuyNHoldValues[sym][0]) * 100; }
    private void HossaBessaStrategy() {}
    

  }
}
